package repo

import (
	"context"
	"path"
	"path/filepath"
	"sync"
	"time"

	"gitee.com/linxing_3/sr-core/domain/dao/scope"
	"gitee.com/linxing_3/sr-core/utils/crypto"
	"gitee.com/mkwchecking/sr-admin/apps/assets/conf"
	"gitee.com/mkwchecking/sr-admin/apps/assets/entity"
	"gitee.com/mkwchecking/sr-admin/pkg/ffmpeg"
	"gitee.com/mkwchecking/sr-admin/pkg/file"
	"gitee.com/mkwchecking/sr-admin/pkg/file/image"
	"gitee.com/mkwchecking/sr-admin/pkg/file/scanner"
	"gitee.com/mkwchecking/sr-admin/pkg/file/video"
	"gorm.io/gorm"
)

var _ scanner.IFileHandler = (*AssetsScanHandler)(nil)

func NewAssetScanHandler(fileRepo *AssetsFile, dirRepo *AssetsDir) *AssetsScanHandler {

	cfg := conf.Conf.AssetsAppCfg.Scanner
	thumbDir := &ThumbDirMan{
		cfg: cfg,
	}

	ffmCli := ffmpeg.LocalFFMpeg(cfg.FFMpeg)
	probCli := ffmpeg.LocalFFprob(cfg.FFprobe)

	h := &AssetsScanHandler{
		cfg:          cfg,
		thumbDirMan:  thumbDir,
		fileRepo:     fileRepo,
		dirRepo:      dirRepo,
		dirCache:     &sync.Map{},
		imageHandler: image.NewImageHandler(ffmCli),
		videoHandler: video.NewHandler(&video.HandleOption{
			PreviewSegments: 3,
		}, ffmCli, probCli, nil),
	}

	return h
}

// AssetsScanHandler 扫描器
type AssetsScanHandler struct {
	cfg conf.AssetsScanCfg

	fileRepo    *AssetsFile
	dirRepo     *AssetsDir
	thumbDirMan *ThumbDirMan

	imageHandler *image.ImageHandler
	videoHandler *video.VideoHandler

	dirCache *sync.Map
}

// OnHandleFolder implements scanner.IFileHandler.
func (a *AssetsScanHandler) OnHandleFolder(base *scanner.BaseAny) error {
	dirInfo, err := a.getDirInfo(base.Path)
	if err != nil {
		return err
	}

	if dirInfo == nil {
		// 新建
		dirInfo = &entity.AssetsDir{
			Name:      base.Name,
			Path:      base.Path,
			ShortCode: crypto.MD5Str(base.Path),
		}
		nt := time.Now()
		dirInfo.ScanTime = &nt
		pDirPath := file.GetParentDir(base.Path)
		if pDir, perr := a.getDirInfo(pDirPath); perr == nil && pDir != nil {
			dirInfo.Pid = pDir.Id
		}

		_, err := a.dirRepo.Create(dirInfo, scope.WithConflictUpdateAll("short_code"))
		return err
	}

	return nil
}

func (a *AssetsScanHandler) getDirInfo(path string) (*entity.AssetsDir, error) {

	// 缓存查
	if dir, ok := a.dirCache.Load(path); ok {
		return dir.(*entity.AssetsDir), nil
	}

	// db查
	dir, err := a.dirRepo.Find(a.dirRepo.EntBuilder(func(ad *entity.AssetsDir) {
		ad.ShortCode = crypto.MD5Str(path)
	}))

	if err != nil && err != gorm.ErrRecordNotFound {
		return nil, err
	} else if err == gorm.ErrRecordNotFound {
		return nil, nil
	}

	if dir != nil {
		// 缓存
		a.dirCache.Store(path, dir)
	}

	return dir, nil
}

// OnFinished implements scanner.IFileHandler.
func (a *AssetsScanHandler) OnFinished(ctx context.Context, opt scanner.ScanOption) error {
	a.Clear(opt.Paths...)
	return nil
}

func (a *AssetsScanHandler) Clear(keys ...string) {
	if len(keys) == 0 {
		a.dirCache.Clear()
		return
	} else {
		for _, key := range keys {
			a.dirCache.Delete(key)
		}
	}
}

func (a *AssetsScanHandler) scanFileToEnt(file *scanner.BaseFile) *entity.AssetsFile {
	return &entity.AssetsFile{
		Name:      file.Name,
		Path:      file.Path,
		Ext:       file.Ext,
		Size:      file.Size,
		Hash:      file.Hash,
		ModTime:   file.ModTime,
		Author:    file.Author,
		FileType:  file.FileType,
		ShortCode: crypto.MD5Str(file.Path),
	}
}

// OnHandleFile implements scanner.IFileHandler.
func (a *AssetsScanHandler) OnHandleFile(file *scanner.BaseFile) error {
	assetFile := a.scanFileToEnt(file)
	return a.upsertFile(assetFile)
}

func (a *AssetsScanHandler) upsertFile(entFile *entity.AssetsFile) error {

	fileDir := filepath.Dir(entFile.Path)
	if dir, _ := a.getDirInfo(fileDir); dir != nil {
		entFile.DirId = dir.Id
	}

	_, err := a.fileRepo.Create(entFile, scope.WithConflictUpdateAll("short_code"))
	return err
}

// OnHandleDocFile implements scanner.IFileHandler.
func (a *AssetsScanHandler) OnHandleDocFile(file *scanner.DocFile) error {
	return a.OnHandleFile(&file.BaseFile)
}

// OnHandleImageFile implements scanner.IFileHandler.
func (a *AssetsScanHandler) OnHandleImageFile(file *scanner.ImageFile) error {
	if a.cfg.ImgThumb {
		full, relate, err := a.thumbDirMan.GetImgCoverGenPath(file.Path)
		if err != nil {
			return err
		}

		err = a.imageHandler.GenCover(context.Background(), file.Path, full)
		if err != nil {
			return err
		}

		entFile := a.scanFileToEnt(&file.BaseFile)
		entFile.ThumbFile = &entity.AssetFileThumb{
			Source:    relate,
			LocalPath: full,
		}
		return a.upsertFile(entFile)
	} else {
		return a.OnHandleFile(&file.BaseFile)
	}
}

// OnHandleVideoFile implements scanner.IFileHandler.
func (a *AssetsScanHandler) OnHandleVideoFile(file *scanner.VideoFile) error {
	entFile := a.scanFileToEnt(&file.BaseFile)
	if a.cfg.VideoThumb {
		full, relate, err := a.thumbDirMan.GetVideoCoverGenPath(file.Path)
		if err != nil {
			return err
		}
		if err := a.videoHandler.GenVideoCover(context.Background(), file.Path, full); err != nil {
			return err
		}

		entFile.ThumbFile = &entity.AssetFileThumb{
			Source:    relate,
			LocalPath: full,
		}
	}

	if a.cfg.VideoClip {
		full, relate, err := a.thumbDirMan.GetVideoSlicesGenPath(file.Path)
		if err != nil {
			return err
		}

		if err := a.videoHandler.GenVideoPreview(context.Background(), file.Path, full); err != nil {
			return err
		}

		entFile.ThumbClip = &entity.AssetFileThumb{
			Source:    relate,
			LocalPath: full,
		}
	}

	return a.upsertFile(entFile)
}

type ThumbDirMan struct {
	cfg conf.AssetsScanCfg
}

// GetImgCoverPath implements image.ImageGenRepo.
func (v *ThumbDirMan) GetImgCoverGenPath(srcPath string) (string, string, error) {
	enc := crypto.MD5Str(srcPath)
	ext := filepath.Ext(srcPath)

	relative := path.Join(enc[:2], enc+ext)
	fullpath := path.Join(v.cfg.ImgThumbDir, relative)
	return fullpath, relative, nil
}

// GetVideoCoverGenPath implements video.IVideoRepo.
func (v *ThumbDirMan) GetVideoCoverGenPath(srcPath string) (string, string, error) {
	enc := crypto.MD5Str(srcPath)

	relative := path.Join(enc[:2], enc+".jpeg")
	fullPath := path.Join(v.cfg.ImgThumbDir, relative)
	return fullPath, relative, nil
}

// GetVideoSlicesGenPath implements video.IVideoRepo.
func (v *ThumbDirMan) GetVideoSlicesGenPath(srcPath string) (string, string, error) {
	enc := crypto.MD5Str(srcPath)
	ext := filepath.Ext(srcPath)

	relatePath := path.Join(enc[:2], enc+ext)
	fullPath := path.Join(v.cfg.VideoClipDir, relatePath)
	return fullPath, relatePath, nil
}
